/* xgettext C/C++/ObjectiveC backend.
   Copyright (C) 1995-2025 Free Software Foundation, Inc.

   This file was written by Peter Miller <millerp@canb.auug.org.au>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#include <config.h>

/* Specification.  */
#include "x-c.h"

#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SB_NO_APPENDF
#include <error.h>
#include "attribute.h"
#include "message.h"
#include "rc-str-list.h"
#include "xgettext.h"
#include "xg-pos.h"
#include "xg-encoding.h"
#include "xg-mixed-string.h"
#include "xg-arglist-context.h"
#include "xg-arglist-callshape.h"
#include "xg-arglist-parser.h"
#include "xg-message.h"
#include "if-error.h"
#include "xalloc.h"
#include "xvasprintf.h"
#include "string-buffer.h"
#include "mem-hash-map.h"
#include "po-charset.h"
#include "gettext.h"

#define _(s) gettext(s)

#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))


/* The ANSI C standard defines several phases of translation:

   1. Terminate line by \n, regardless of the external representation
      of a text line.  Stdio does this for us.

   2. Convert trigraphs to their single character equivalents.

   3. Concatenate each line ending in backslash (\) with the following
      line.

   4. Replace each comment with a space character.

   5. Parse each resulting logical line as preprocessing tokens a
      white space.

   6. Recognize and carry out directives (it also expands macros on
      non-directive lines, which we do not do here).

   7. Replaces escape sequences within character strings with their
      single character equivalents (we do this in step 5, because we
      don't have to worry about the #include argument).

   8. Concatenates adjacent string literals to form single string
      literals (because we don't expand macros, there are a few things
      we will miss).

   9. Converts the remaining preprocessing tokens to C tokens and
      discards any white space from the translation unit.

   This lexer implements the above, and presents the scanner (in
   xgettext.c) with a stream of C tokens.  The comments are
   accumulated in a buffer, and given to xgettext when asked for.  */


/* ========================= Lexer customization.  ========================= */

static bool trigraphs = false;

void
x_c_trigraphs ()
{
  trigraphs = true;
}


/* ====================== Keyword set customization.  ====================== */

/* If true extract all strings.  */
static bool extract_all = false;

static hash_table c_keywords;
static hash_table objc_keywords;
static bool default_keywords = true;


void
x_c_extract_all ()
{
  extract_all = true;
}


static void
add_keyword (const char *name, hash_table *keywords)
{
  if (name == NULL)
    default_keywords = false;
  else
    {
      const char *end;
      struct callshape shape;
      const char *colon;

      if (keywords->table == NULL)
        hash_init (keywords, 100);

      split_keywordspec (name, &end, &shape);

      /* The characters between name and end should form a valid C identifier.
         A colon means an invalid parse in split_keywordspec().  */
      colon = strchr (name, ':');
      if (colon == NULL || colon >= end)
        insert_keyword_callshape (keywords, name, end - name, &shape);
    }
}

void
x_c_keyword (const char *name)
{
  add_keyword (name, &c_keywords);
}

void
x_objc_keyword (const char *name)
{
  add_keyword (name, &objc_keywords);
}

static bool additional_keywords_kde;

void
activate_additional_keywords_kde ()
{
  additional_keywords_kde = true;
}

/* Finish initializing the keywords hash tables.
   Called after argument processing, before each file is processed.  */
static void
init_keywords ()
{
  if (default_keywords)
    {
      /* When adding new keywords here, also update the documentation in
         xgettext.texi!  */
      x_c_keyword ("gettext");
      x_c_keyword ("dgettext:2");
      x_c_keyword ("dcgettext:2");
      x_c_keyword ("ngettext:1,2");
      x_c_keyword ("dngettext:2,3");
      x_c_keyword ("dcngettext:2,3");
      x_c_keyword ("gettext_noop");
      x_c_keyword ("pgettext:1c,2");
      x_c_keyword ("dpgettext:2c,3");
      x_c_keyword ("dcpgettext:2c,3");
      x_c_keyword ("npgettext:1c,2,3");
      x_c_keyword ("dnpgettext:2c,3,4");
      x_c_keyword ("dcnpgettext:2c,3,4");

      if (additional_keywords_kde)
        {
          x_c_keyword ("i18n:1");
          x_c_keyword ("i18nc:1c,2");
          x_c_keyword ("i18np:1,2");
          x_c_keyword ("i18ncp:1c,2,3");
          x_c_keyword ("i18nd:2");
          x_c_keyword ("i18ndc:2c,3");
          x_c_keyword ("i18ndp:2,3");
          x_c_keyword ("i18ndcp:2c,3,4");
          x_c_keyword ("ki18n:1");
          x_c_keyword ("ki18nc:1c,2");
          x_c_keyword ("ki18np:1,2");
          x_c_keyword ("ki18ncp:1c,2,3");
          x_c_keyword ("ki18nd:2");
          x_c_keyword ("ki18ndc:2c,3");
          x_c_keyword ("ki18ndp:2,3");
          x_c_keyword ("ki18ndcp:2c,3,4");
          x_c_keyword ("I18N_NOOP:1");
          x_c_keyword ("I18NC_NOOP:1c,2");
          x_c_keyword ("I18N_NOOP2:1c,2");
          x_c_keyword ("I18N_NOOP2_NOSTRIP:1c,2");
          x_c_keyword ("xi18n:1");
          x_c_keyword ("xi18nc:1c,2");
          x_c_keyword ("xi18np:1,2");
          x_c_keyword ("xi18ncp:1c,2,3");
          x_c_keyword ("xi18nd:2");
          x_c_keyword ("xi18ndc:2c,3");
          x_c_keyword ("xi18ndp:2,3");
          x_c_keyword ("xi18ndcp:2c,3,4");
          x_c_keyword ("kxi18n:1");
          x_c_keyword ("kxi18nc:1c,2");
          x_c_keyword ("kxi18np:1,2");
          x_c_keyword ("kxi18ncp:1c,2,3");
          x_c_keyword ("kxi18nd:2");
          x_c_keyword ("kxi18ndc:2c,3");
          x_c_keyword ("kxi18ndp:2,3");
          x_c_keyword ("kxi18ndcp:2c,3,4");
          x_c_keyword ("XI18N_NOOP:1");
          x_c_keyword ("XI18NC_NOOP:1c,2");
          x_c_keyword ("XI18N_NOOP2:1c,2");
          x_c_keyword ("XI18N_NOOP2_NOSTRIP:1c,2");
        }

      x_objc_keyword ("gettext");
      x_objc_keyword ("dgettext:2");
      x_objc_keyword ("dcgettext:2");
      x_objc_keyword ("ngettext:1,2");
      x_objc_keyword ("dngettext:2,3");
      x_objc_keyword ("dcngettext:2,3");
      x_objc_keyword ("gettext_noop");
      x_objc_keyword ("pgettext:1c,2");
      x_objc_keyword ("dpgettext:2c,3");
      x_objc_keyword ("dcpgettext:2c,3");
      x_objc_keyword ("npgettext:1c,2,3");
      x_objc_keyword ("dnpgettext:2c,3,4");
      x_objc_keyword ("dcnpgettext:2c,3,4");
      x_objc_keyword ("NSLocalizedString");       /* similar to gettext */
      x_objc_keyword ("_");                       /* similar to gettext */
      x_objc_keyword ("NSLocalizedStaticString"); /* similar to gettext_noop */
      x_objc_keyword ("__");                      /* similar to gettext_noop */

      default_keywords = false;
    }
}

void
init_flag_table_c ()
{
  xgettext_record_flag ("gettext:1:pass-c-format");
  xgettext_record_flag ("dgettext:2:pass-c-format");
  xgettext_record_flag ("dcgettext:2:pass-c-format");
  xgettext_record_flag ("ngettext:1:pass-c-format");
  xgettext_record_flag ("ngettext:2:pass-c-format");
  xgettext_record_flag ("dngettext:2:pass-c-format");
  xgettext_record_flag ("dngettext:3:pass-c-format");
  xgettext_record_flag ("dcngettext:2:pass-c-format");
  xgettext_record_flag ("dcngettext:3:pass-c-format");
  xgettext_record_flag ("gettext_noop:1:pass-c-format");
  xgettext_record_flag ("pgettext:2:pass-c-format");
  xgettext_record_flag ("dpgettext:3:pass-c-format");
  xgettext_record_flag ("dcpgettext:3:pass-c-format");
  xgettext_record_flag ("npgettext:2:pass-c-format");
  xgettext_record_flag ("npgettext:3:pass-c-format");
  xgettext_record_flag ("dnpgettext:3:pass-c-format");
  xgettext_record_flag ("dnpgettext:4:pass-c-format");
  xgettext_record_flag ("dcnpgettext:3:pass-c-format");
  xgettext_record_flag ("dcnpgettext:4:pass-c-format");

  /* <stdio.h> */
  xgettext_record_flag ("fprintf:2:c-format");
  xgettext_record_flag ("vfprintf:2:c-format");
  xgettext_record_flag ("printf:1:c-format");
  xgettext_record_flag ("vprintf:1:c-format");
  xgettext_record_flag ("sprintf:2:c-format");
  xgettext_record_flag ("vsprintf:2:c-format");
  xgettext_record_flag ("snprintf:3:c-format");
  xgettext_record_flag ("vsnprintf:3:c-format");
#if 0 /* These functions are not standard.  */
  /* <stdio.h> */
  xgettext_record_flag ("asprintf:2:c-format");
  xgettext_record_flag ("vasprintf:2:c-format");
  xgettext_record_flag ("dprintf:2:c-format");
  xgettext_record_flag ("vdprintf:2:c-format");
  xgettext_record_flag ("obstack_printf:2:c-format");
  xgettext_record_flag ("obstack_vprintf:2:c-format");
  /* <error.h> */
  xgettext_record_flag ("error:3:c-format");
  xgettext_record_flag ("error_at_line:5:c-format");
  /* <argp.h> */
  xgettext_record_flag ("argp_error:2:c-format");
  xgettext_record_flag ("argp_failure:2:c-format");
#endif

  xgettext_record_flag ("gettext:1:pass-c++-format");
  xgettext_record_flag ("dgettext:2:pass-c++-format");
  xgettext_record_flag ("dcgettext:2:pass-c++-format");
  xgettext_record_flag ("ngettext:1:pass-c++-format");
  xgettext_record_flag ("ngettext:2:pass-c++-format");
  xgettext_record_flag ("dngettext:2:pass-c++-format");
  xgettext_record_flag ("dngettext:3:pass-c++-format");
  xgettext_record_flag ("dcngettext:2:pass-c++-format");
  xgettext_record_flag ("dcngettext:3:pass-c++-format");
  xgettext_record_flag ("gettext_noop:1:pass-c++-format");
  xgettext_record_flag ("pgettext:2:pass-c++-format");
  xgettext_record_flag ("dpgettext:3:pass-c++-format");
  xgettext_record_flag ("dcpgettext:3:pass-c++-format");
  xgettext_record_flag ("npgettext:2:pass-c++-format");
  xgettext_record_flag ("npgettext:3:pass-c++-format");
  xgettext_record_flag ("dnpgettext:3:pass-c++-format");
  xgettext_record_flag ("dnpgettext:4:pass-c++-format");
  xgettext_record_flag ("dcnpgettext:3:pass-c++-format");
  xgettext_record_flag ("dcnpgettext:4:pass-c++-format");

  /* C++ <format> */
  xgettext_record_flag ("vformat:1:c++-format");
  xgettext_record_flag ("vformat_to:2:c++-format");

  xgettext_record_flag ("gettext:1:pass-qt-format");
  xgettext_record_flag ("dgettext:2:pass-qt-format");
  xgettext_record_flag ("dcgettext:2:pass-qt-format");
  xgettext_record_flag ("ngettext:1:pass-qt-format");
  xgettext_record_flag ("ngettext:2:pass-qt-format");
  xgettext_record_flag ("dngettext:2:pass-qt-format");
  xgettext_record_flag ("dngettext:3:pass-qt-format");
  xgettext_record_flag ("dcngettext:2:pass-qt-format");
  xgettext_record_flag ("dcngettext:3:pass-qt-format");
  xgettext_record_flag ("gettext_noop:1:pass-qt-format");
  xgettext_record_flag ("pgettext:2:pass-qt-format");
  xgettext_record_flag ("dpgettext:3:pass-qt-format");
  xgettext_record_flag ("dcpgettext:3:pass-qt-format");
  xgettext_record_flag ("npgettext:2:pass-qt-format");
  xgettext_record_flag ("npgettext:3:pass-qt-format");
  xgettext_record_flag ("dnpgettext:3:pass-qt-format");
  xgettext_record_flag ("dnpgettext:4:pass-qt-format");
  xgettext_record_flag ("dcnpgettext:3:pass-qt-format");
  xgettext_record_flag ("dcnpgettext:4:pass-qt-format");

  xgettext_record_flag ("gettext:1:pass-kde-format");
  xgettext_record_flag ("dgettext:2:pass-kde-format");
  xgettext_record_flag ("dcgettext:2:pass-kde-format");
  xgettext_record_flag ("ngettext:1:pass-kde-format");
  xgettext_record_flag ("ngettext:2:pass-kde-format");
  xgettext_record_flag ("dngettext:2:pass-kde-format");
  xgettext_record_flag ("dngettext:3:pass-kde-format");
  xgettext_record_flag ("dcngettext:2:pass-kde-format");
  xgettext_record_flag ("dcngettext:3:pass-kde-format");
  xgettext_record_flag ("gettext_noop:1:pass-kde-format");
  xgettext_record_flag ("pgettext:2:pass-kde-format");
  xgettext_record_flag ("dpgettext:3:pass-kde-format");
  xgettext_record_flag ("dcpgettext:3:pass-kde-format");
  xgettext_record_flag ("npgettext:2:pass-kde-format");
  xgettext_record_flag ("npgettext:3:pass-kde-format");
  xgettext_record_flag ("dnpgettext:3:pass-kde-format");
  xgettext_record_flag ("dnpgettext:4:pass-kde-format");
  xgettext_record_flag ("dcnpgettext:3:pass-kde-format");
  xgettext_record_flag ("dcnpgettext:4:pass-kde-format");

  xgettext_record_flag ("gettext:1:pass-boost-format");
  xgettext_record_flag ("dgettext:2:pass-boost-format");
  xgettext_record_flag ("dcgettext:2:pass-boost-format");
  xgettext_record_flag ("ngettext:1:pass-boost-format");
  xgettext_record_flag ("ngettext:2:pass-boost-format");
  xgettext_record_flag ("dngettext:2:pass-boost-format");
  xgettext_record_flag ("dngettext:3:pass-boost-format");
  xgettext_record_flag ("dcngettext:2:pass-boost-format");
  xgettext_record_flag ("dcngettext:3:pass-boost-format");
  xgettext_record_flag ("gettext_noop:1:pass-boost-format");
  xgettext_record_flag ("pgettext:2:pass-boost-format");
  xgettext_record_flag ("dpgettext:3:pass-boost-format");
  xgettext_record_flag ("dcpgettext:3:pass-boost-format");
  xgettext_record_flag ("npgettext:2:pass-boost-format");
  xgettext_record_flag ("npgettext:3:pass-boost-format");
  xgettext_record_flag ("dnpgettext:3:pass-boost-format");
  xgettext_record_flag ("dnpgettext:4:pass-boost-format");
  xgettext_record_flag ("dcnpgettext:3:pass-boost-format");
  xgettext_record_flag ("dcnpgettext:4:pass-boost-format");

  /* <boost/format.hpp> */
  xgettext_record_flag ("format:1:boost-format");
}

void
init_flag_table_objc ()
{
  /* Since the settings done in init_flag_table_c() also have an effect for
     the ObjectiveC parser, we don't have to repeat them here.  */
  xgettext_record_flag ("gettext:1:pass-objc-format");
  xgettext_record_flag ("dgettext:2:pass-objc-format");
  xgettext_record_flag ("dcgettext:2:pass-objc-format");
  xgettext_record_flag ("ngettext:1:pass-objc-format");
  xgettext_record_flag ("ngettext:2:pass-objc-format");
  xgettext_record_flag ("dngettext:2:pass-objc-format");
  xgettext_record_flag ("dngettext:3:pass-objc-format");
  xgettext_record_flag ("dcngettext:2:pass-objc-format");
  xgettext_record_flag ("dcngettext:3:pass-objc-format");
  xgettext_record_flag ("gettext_noop:1:pass-objc-format");
  xgettext_record_flag ("pgettext:2:pass-objc-format");
  xgettext_record_flag ("dpgettext:3:pass-objc-format");
  xgettext_record_flag ("dcpgettext:3:pass-objc-format");
  xgettext_record_flag ("npgettext:2:pass-objc-format");
  xgettext_record_flag ("npgettext:3:pass-objc-format");
  xgettext_record_flag ("dnpgettext:3:pass-objc-format");
  xgettext_record_flag ("dnpgettext:4:pass-objc-format");
  xgettext_record_flag ("dcnpgettext:3:pass-objc-format");
  xgettext_record_flag ("dcnpgettext:4:pass-objc-format");
  xgettext_record_flag ("NSLocalizedString:1:pass-c-format");
  xgettext_record_flag ("NSLocalizedString:1:pass-objc-format");
  xgettext_record_flag ("_:1:pass-c-format");
  xgettext_record_flag ("_:1:pass-objc-format");
  xgettext_record_flag ("stringWithFormat::1:objc-format");
  xgettext_record_flag ("initWithFormat::1:objc-format");
  xgettext_record_flag ("stringByAppendingFormat::1:objc-format");
  xgettext_record_flag ("localizedStringWithFormat::1:objc-format");
  xgettext_record_flag ("appendFormat::1:objc-format");
}

void
init_flag_table_gcc_internal ()
{
  xgettext_record_flag ("gettext:1:pass-gcc-internal-format");
  xgettext_record_flag ("dgettext:2:pass-gcc-internal-format");
  xgettext_record_flag ("dcgettext:2:pass-gcc-internal-format");
  xgettext_record_flag ("ngettext:1:pass-gcc-internal-format");
  xgettext_record_flag ("ngettext:2:pass-gcc-internal-format");
  xgettext_record_flag ("dngettext:2:pass-gcc-internal-format");
  xgettext_record_flag ("dngettext:3:pass-gcc-internal-format");
  xgettext_record_flag ("dcngettext:2:pass-gcc-internal-format");
  xgettext_record_flag ("dcngettext:3:pass-gcc-internal-format");
  xgettext_record_flag ("gettext_noop:1:pass-gcc-internal-format");
  xgettext_record_flag ("pgettext:2:pass-gcc-internal-format");
  xgettext_record_flag ("dpgettext:3:pass-gcc-internal-format");
  xgettext_record_flag ("dcpgettext:3:pass-gcc-internal-format");
  xgettext_record_flag ("npgettext:2:pass-gcc-internal-format");
  xgettext_record_flag ("npgettext:3:pass-gcc-internal-format");
  xgettext_record_flag ("dnpgettext:3:pass-gcc-internal-format");
  xgettext_record_flag ("dnpgettext:4:pass-gcc-internal-format");
  xgettext_record_flag ("dcnpgettext:3:pass-gcc-internal-format");
  xgettext_record_flag ("dcnpgettext:4:pass-gcc-internal-format");
#if 0 /* This should better be done inside GCC.  */
  /* grepping for ATTRIBUTE_PRINTF in gcc-3.3/gcc/?*.h */
  /* c-format.c */
  xgettext_record_flag ("status_warning:2:gcc-internal-format");
  /* c-tree.h */
  xgettext_record_flag ("pedwarn_c99:1:pass-gcc-internal-format");
  /* collect2.h */
  //xgettext_record_flag ("error:1:c-format"); // 3 different versions
  xgettext_record_flag ("notice:1:c-format");
  //xgettext_record_flag ("fatal:1:c-format"); // 2 different versions
  xgettext_record_flag ("fatal_perror:1:c-format");
  /* cpplib.h */
  xgettext_record_flag ("cpp_error:3:c-format");
  xgettext_record_flag ("cpp_error_with_line:5:c-format");
  /* diagnostic.h */
  xgettext_record_flag ("diagnostic_set_info:2:pass-gcc-internal-format");
  xgettext_record_flag ("output_printf:2:gcc-internal-format");
  xgettext_record_flag ("output_verbatim:2:pass-gcc-internal-format");
  xgettext_record_flag ("verbatim:1:gcc-internal-format");
  xgettext_record_flag ("inform:1:pass-gcc-internal-format");
  /* gcc.h */
  //xgettext_record_flag ("fatal:1:c-format"); // 2 different versions
  //xgettext_record_flag ("error:1:c-format"); // 3 different versions
  /* genattrtab.h */
  xgettext_record_flag ("attr_printf:2:pass-c-format");
  /* gengtype.h */
  xgettext_record_flag ("error_at_line:2:pass-c-format");
  xgettext_record_flag ("xvasprintf:2:pass-c-format");
  xgettext_record_flag ("xasprintf:1:pass-c-format");
  xgettext_record_flag ("oprintf:2:pass-c-format");
  /* gensupport.h */
  xgettext_record_flag ("message_with_line:2:pass-c-format");
  /* output.h */
  xgettext_record_flag ("output_operand_lossage:1:c-format");
  /* ra.h */
   xgettext_record_flag ("ra_debug_msg:2:pass-c-format");
  /* toplev.h */
  xgettext_record_flag ("fnotice:2:c-format");
  xgettext_record_flag ("fatal_io_error:2:gcc-internal-format");
  xgettext_record_flag ("error_for_asm:2:pass-gcc-internal-format");
  xgettext_record_flag ("warning_for_asm:2:pass-gcc-internal-format");
  xgettext_record_flag ("error_with_file_and_line:3:pass-gcc-internal-format");
  xgettext_record_flag ("error_with_decl:2:pass-gcc-internal-format");
  xgettext_record_flag ("pedwarn:1:gcc-internal-format");
  xgettext_record_flag ("pedwarn_with_file_and_line:3:gcc-internal-format");
  xgettext_record_flag ("pedwarn_with_decl:2:gcc-internal-format");
  xgettext_record_flag ("sorry:1:gcc-internal-format");
  xgettext_record_flag ("error:1:pass-gcc-internal-format");
  xgettext_record_flag ("fatal_error:1:pass-gcc-internal-format");
  xgettext_record_flag ("internal_error:1:pass-gcc-internal-format");
  xgettext_record_flag ("warning:1:pass-gcc-internal-format");
  xgettext_record_flag ("warning_with_file_and_line:3:pass-gcc-internal-format");
  xgettext_record_flag ("warning_with_decl:2:pass-gcc-internal-format");
  /* f/com.h */
  xgettext_record_flag ("ffecom_get_invented_identifier:1:pass-c-format");
  /* f/sts.h */
  xgettext_record_flag ("ffests_printf:2:pass-c-format");
  /* java/java-tree.h */
  xgettext_record_flag ("parse_error_context:2:pass-c-format");
#endif

  xgettext_record_flag ("gettext:1:pass-gfc-internal-format");
  xgettext_record_flag ("dgettext:2:pass-gfc-internal-format");
  xgettext_record_flag ("dcgettext:2:pass-gfc-internal-format");
  xgettext_record_flag ("ngettext:1:pass-gfc-internal-format");
  xgettext_record_flag ("ngettext:2:pass-gfc-internal-format");
  xgettext_record_flag ("dngettext:2:pass-gfc-internal-format");
  xgettext_record_flag ("dngettext:3:pass-gfc-internal-format");
  xgettext_record_flag ("dcngettext:2:pass-gfc-internal-format");
  xgettext_record_flag ("dcngettext:3:pass-gfc-internal-format");
  xgettext_record_flag ("gettext_noop:1:pass-gfc-internal-format");
  xgettext_record_flag ("pgettext:2:pass-gfc-internal-format");
  xgettext_record_flag ("dpgettext:3:pass-gfc-internal-format");
  xgettext_record_flag ("dcpgettext:3:pass-gfc-internal-format");
  xgettext_record_flag ("npgettext:2:pass-gfc-internal-format");
  xgettext_record_flag ("npgettext:3:pass-gfc-internal-format");
  xgettext_record_flag ("dnpgettext:3:pass-gfc-internal-format");
  xgettext_record_flag ("dnpgettext:4:pass-gfc-internal-format");
  xgettext_record_flag ("dcnpgettext:3:pass-gfc-internal-format");
  xgettext_record_flag ("dcnpgettext:4:pass-gfc-internal-format");
#if 0 /* This should better be done inside GCC.  */
  /* fortran/error.c */
  xgettext_record_flag ("gfc_error:1:gfc-internal-format");
  xgettext_record_flag ("gfc_error_now:1:gfc-internal-format");
  xgettext_record_flag ("gfc_fatal_error:1:gfc-internal-format");
  xgettext_record_flag ("gfc_internal_error:1:gfc-internal-format");
  xgettext_record_flag ("gfc_notify_std:2:gfc-internal-format");
  xgettext_record_flag ("gfc_warning:1:gfc-internal-format");
  xgettext_record_flag ("gfc_warning_now:1:gfc-internal-format");
#endif
}

void
init_flag_table_kde ()
{
  xgettext_record_flag ("i18n:1:kde-format");
  xgettext_record_flag ("i18nc:2:kde-format");
  xgettext_record_flag ("i18np:1:kde-format");
  xgettext_record_flag ("i18ncp:2:kde-format");
  xgettext_record_flag ("i18nd:2:kde-format");
  xgettext_record_flag ("i18ndc:3:kde-format");
  xgettext_record_flag ("i18ndp:2:kde-format");
  xgettext_record_flag ("i18ndcp:3:kde-format");
  xgettext_record_flag ("ki18n:1:kde-format");
  xgettext_record_flag ("ki18nc:2:kde-format");
  xgettext_record_flag ("ki18np:1:kde-format");
  xgettext_record_flag ("ki18ncp:2:kde-format");
  xgettext_record_flag ("ki18nd:2:kde-format");
  xgettext_record_flag ("ki18ndc:3:kde-format");
  xgettext_record_flag ("ki18ndp:2:kde-format");
  xgettext_record_flag ("ki18ndcp:3:kde-format");
  xgettext_record_flag ("I18N_NOOP:1:kde-format");
  xgettext_record_flag ("I18NC_NOOP:2:kde-format");
  xgettext_record_flag ("I18N_NOOP2:2:kde-format");
  xgettext_record_flag ("I18N_NOOP2_NOSTRIP:2:kde-format");
  xgettext_record_flag ("xi18n:1:kde-kuit-format");
  xgettext_record_flag ("xi18nc:2:kde-kuit-format");
  xgettext_record_flag ("xi18np:1:kde-kuit-format");
  xgettext_record_flag ("xi18ncp:2:kde-kuit-format");
  xgettext_record_flag ("xi18nd:2:kde-kuit-format");
  xgettext_record_flag ("xi18ndc:3:kde-kuit-format");
  xgettext_record_flag ("xi18ndp:2:kde-kuit-format");
  xgettext_record_flag ("xi18ndcp:3:kde-kuit-format");
  xgettext_record_flag ("kxi18n:1:kde-kuit-format");
  xgettext_record_flag ("kxi18nc:2:kde-kuit-format");
  xgettext_record_flag ("kxi18np:1:kde-kuit-format");
  xgettext_record_flag ("kxi18ncp:2:kde-kuit-format");
  xgettext_record_flag ("kxi18nd:2:kde-kuit-format");
  xgettext_record_flag ("kxi18ndc:3:kde-kuit-format");
  xgettext_record_flag ("kxi18ndp:2:kde-kuit-format");
  xgettext_record_flag ("kxi18ndcp:3:kde-kuit-format");
  xgettext_record_flag ("XI18N_NOOP:1:kde-kuit-format");
  xgettext_record_flag ("XI18NC_NOOP:2:kde-kuit-format");
  xgettext_record_flag ("XI18N_NOOP2:2:kde-kuit-format");
  xgettext_record_flag ("XI18N_NOOP2_NOSTRIP:2:kde-kuit-format");
}

/* ======================== Reading of characters.  ======================== */

/* The input file stream.  */
static FILE *fp;


/* 0. Terminate line by \n, regardless whether the external representation of
   a line terminator is LF (Unix), CR (Mac) or CR/LF (DOS/Windows).
   It is debatable whether supporting CR/LF line terminators in C sources
   on Unix is ISO C or POSIX compliant, but since GCC 3.3 now supports it
   unconditionally, it must be OK.
   The so-called "text mode" in stdio on DOS/Windows translates CR/LF to \n
   automatically, but here we also need this conversion on Unix.  As a side
   effect, on DOS/Windows we also parse CR/CR/LF into a single \n, but this
   is not a problem.  */


static int
phase0_getc ()
{
  int c;

  c = getc (fp);
  if (c == EOF)
    {
      if (ferror (fp))
        error (EXIT_FAILURE, errno, _("error while reading \"%s\""),
               real_file_name);
      return EOF;
    }

  if (c == '\r')
    {
      int c1 = getc (fp);

      if (c1 != EOF && c1 != '\n')
        ungetc (c1, fp);

      /* Seen line terminator CR or CR/LF.  */
      return '\n';
    }

  return c;
}


/* Supports only one pushback character, and not '\n'.  */
static inline void
phase0_ungetc (int c)
{
  if (c != EOF)
    ungetc (c, fp);
}


/* 1. line_number handling.  Combine backslash-newline to nothing.  */

static unsigned char phase1_pushback[2];
static int phase1_pushback_length;


static int
phase1_getc ()
{
  int c;

  if (phase1_pushback_length)
    {
      c = phase1_pushback[--phase1_pushback_length];
      if (c == '\n')
        ++line_number;
      return c;
    }
  for (;;)
    {
      c = phase0_getc ();
      switch (c)
        {
        case '\n':
          ++line_number;
          return '\n';

        case '\\':
          c = phase0_getc ();
          if (c != '\n')
            {
              phase0_ungetc (c);
              return '\\';
            }
          ++line_number;
          break;

        default:
          return c;
        }
    }
}


/* Supports 2 characters of pushback.  */
static void
phase1_ungetc (int c)
{
  switch (c)
    {
    case EOF:
      break;

    case '\n':
      --line_number;
      FALLTHROUGH;

    default:
      if (phase1_pushback_length == SIZEOF (phase1_pushback))
        abort ();
      phase1_pushback[phase1_pushback_length++] = c;
      break;
    }
}


/* 2. Convert trigraphs to their single character equivalents.  Most
   sane human beings vomit copiously at the mention of trigraphs, which
   is why they are an option.  */

static unsigned char phase2_pushback[1];
static int phase2_pushback_length;


static int
phase2_getc ()
{
  int c;

  if (phase2_pushback_length)
    return phase2_pushback[--phase2_pushback_length];
  if (!trigraphs)
    return phase1_getc ();

  c = phase1_getc ();
  if (c != '?')
    return c;
  c = phase1_getc ();
  if (c != '?')
    {
      phase1_ungetc (c);
      return '?';
    }
  c = phase1_getc ();
  switch (c)
    {
    case '(':
      return '[';
    case '/':
      return '\\';
    case ')':
      return ']';
    case '\'':
      return '^';
    case '<':
      return '{';
    case '!':
      return '|';
    case '>':
      return '}';
    case '-':
      return '~';
    case '#':
      return '=';
    }
  phase1_ungetc (c);
  phase1_ungetc ('?');
  return '?';
}


/* Supports only one pushback character.  */
static void
phase2_ungetc (int c)
{
  if (c != EOF)
    {
      if (phase2_pushback_length == SIZEOF (phase2_pushback))
        abort ();
      phase2_pushback[phase2_pushback_length++] = c;
    }
}


/* 3. Concatenate each line ending in backslash (\) with the following
   line.  Basically, all you need to do is elide "\\\n" sequences from
   the input.  */

static unsigned char phase3_pushback[9];
static int phase3_pushback_length;


static int
phase3_getc ()
{
  if (phase3_pushback_length)
    return phase3_pushback[--phase3_pushback_length];
  for (;;)
    {
      int c = phase2_getc ();
      if (c != '\\')
        return c;
      c = phase2_getc ();
      if (c != '\n')
        {
          phase2_ungetc (c);
          return '\\';
        }
    }
}


/* Supports 9 characters of pushback.  */
static void
phase3_ungetc (int c)
{
  if (c != EOF)
    {
      if (phase3_pushback_length == SIZEOF (phase3_pushback))
        abort ();
      phase3_pushback[phase3_pushback_length++] = c;
    }
}


/* Accumulating comments.  */

static char *buffer;
static size_t bufmax;
static size_t buflen;

static inline void
comment_start ()
{
  buflen = 0;
}

static inline void
comment_add (int c)
{
  if (buflen >= bufmax)
    {
      bufmax = 2 * bufmax + 10;
      buffer = xrealloc (buffer, bufmax);
    }
  buffer[buflen++] = c;
}

static inline void
comment_line_end (size_t chars_to_remove)
{
  buflen -= chars_to_remove;
  while (buflen >= 1
         && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t'))
    --buflen;
  if (chars_to_remove == 0 && buflen >= bufmax)
    {
      bufmax = 2 * bufmax + 10;
      buffer = xrealloc (buffer, bufmax);
    }
  buffer[buflen] = '\0';
  savable_comment_add (buffer);
}


/* These are for tracking whether comments count as immediately before
   keyword.  */
static int last_comment_line;
static int last_non_comment_line;
static int newline_count;


/* 4. Replace each comment that is not inside a character constant or
   string literal with a space character.  We need to remember the
   comment for later, because it may be attached to a keyword string.
   We also optionally understand C++ comments.  */

static int
phase4_getc ()
{
  int c;
  bool last_was_star;

  c = phase3_getc ();
  if (c != '/')
    return c;
  c = phase3_getc ();
  switch (c)
    {
    default:
      phase3_ungetc (c);
      return '/';

    case '*':
      /* C comment.  */
      comment_start ();
      last_was_star = false;
      for (;;)
        {
          c = phase3_getc ();
          if (c == EOF)
            break;
          /* We skip all leading white space, but not EOLs.  */
          if (!(buflen == 0 && (c == ' ' || c == '\t')))
            comment_add (c);
          switch (c)
            {
            case '\n':
              comment_line_end (1);
              comment_start ();
              last_was_star = false;
              continue;

            case '*':
              last_was_star = true;
              continue;

            case '/':
              if (last_was_star)
                {
                  comment_line_end (2);
                  break;
                }
              FALLTHROUGH;

            default:
              last_was_star = false;
              continue;
            }
          break;
        }
      last_comment_line = newline_count;
      return ' ';

    case '/':
      /* C++ or ISO C 99 comment.  */
      comment_start ();
      for (;;)
        {
          c = phase3_getc ();
          if (c == '\n' || c == EOF)
            break;
          /* We skip all leading white space, but not EOLs.  */
          if (!(buflen == 0 && (c == ' ' || c == '\t')))
            comment_add (c);
        }
      comment_line_end (0);
      last_comment_line = newline_count;
      return '\n';
    }
}


/* Supports only one pushback character.  */
static void
phase4_ungetc (int c)
{
  phase3_ungetc (c);
}


/* ========================== Reading of tokens.  ========================== */


/* True if ObjectiveC extensions are recognized.  */
static bool objc_extensions;

/* True if C++ extensions are recognized.  */
static bool cxx_extensions;

enum token_type_ty
{
  token_type_character_constant,        /* 'x' */
  token_type_eof,
  token_type_eoln,
  token_type_hash,                      /* # */
  token_type_lparen,                    /* ( */
  token_type_rparen,                    /* ) */
  token_type_comma,                     /* , */
  token_type_colon,                     /* : */
  token_type_name,                      /* abc */
  token_type_number,                    /* 2.7 */
  token_type_string_literal,            /* "abc" */
  token_type_symbol,                    /* < > = etc. */
  token_type_objc_special,              /* @ */
  token_type_white_space
};
typedef enum token_type_ty token_type_ty;

typedef struct token_ty token_ty;
struct token_ty
{
  token_type_ty type;
  char *string;                         /* for token_type_name */
  mixed_string_ty *mixed_string;        /* for token_type_string_literal */
  refcounted_string_list_ty *comment;   /* for token_type_string_literal,
                                           token_type_objc_special */
  long number;
  int line_number;
};


/* Replace escape sequences within character strings with their
   single character equivalents.  This is called from phase 5, because
   we don't have to worry about the #include argument.  There are
   pathological cases which could bite us (like the DOS directory
   separator), but just pretend it can't happen.  */

/* Return value of get_string_element when EOF is reached.  */
#define SE_EOF (-1)

/* Replace escape sequences within character strings with their single
   character equivalents.  */
#define SE_QUOTES (-3)
#define SE_QUOTE (-4)
#define SE_NEWLINE (-5)

/* Convert an UTF-16 or UTF-32 code point to a return value that can be
   distinguished from a single-byte return value.  */
#define UNICODE(code) (0x100 + (code))

/* Test a return value of phase7_getuc whether it designates an UTF-16 or
   UTF-32 code point.  */
#define IS_UNICODE(p7_result) ((p7_result) >= 0x100)

/* Extract the UTF-16 or UTF-32 code of a return value that satisfies
   IS_UNICODE.  */
#define UNICODE_VALUE(p7_result) ((p7_result) - 0x100)


/* Parse an element of a character literal or string literal.
   CONTEXT is -1 for a character literal (wide or not),
              0 for a normal string literal,
              1 for a wide string literal.  */
static int
get_string_element (int context)
{
  int c, j;

  /* Use phase 3, because phase 4 elides comments.  */
  c = phase3_getc ();

  if (c == EOF)
    return SE_EOF;

  /* Return a magic newline indicator, so that we can distinguish
     between the user requesting a newline in the string (e.g. using
     "\n" or "\012") from the user failing to terminate the string or
     character constant.  The ANSI C standard says: 3.1.3.4 Character
     Constants contain "any character except single quote, backslash or
     newline; or an escape sequence" and 3.1.4 String Literals contain
     "any character except double quote, backslash or newline; or an
     escape sequence".

     Most compilers give a fatal error in this case, however gcc is
     stupidly silent, even though this is a very common typo.  OK, so
     "gcc --pedantic" will tell me, but that gripes about too much other
     stuff.  Could I have a "gcc -Wnewline-in-string" option, or
     better yet a "gcc -fno-newline-in-string" option, please?  Gcc is
     also inconsistent between string literals and character constants:
     you may not embed newlines in character constants; try it, you get
     a useful diagnostic.  --PMiller  */
  if (c == '\n')
    return SE_NEWLINE;

  if (c == '"')
    return SE_QUOTES;
  if (c == '\'')
    return SE_QUOTE;
  if (c != '\\')
    return c;
  c = phase3_getc ();
  switch (c)
    {
    default:
      /* Invalid escape sequences generate a GCC warning, and GCC transforms
         \c to the character c.  So let's do the same.  */
    case '"':
    case '\'':
    case '?':
    case '\\':
      return c;

    case 'a':
      return '\a';
    case 'b':
      return '\b';

      /* The \e escape is peculiar to gcc, and assumes an ASCII
         character set (or superset).  We don't provide support for it
         here.  */

    case 'f':
      return '\f';
    case 'n':
      return '\n';
    case 'r':
      return '\r';
    case 't':
      return '\t';
    case 'v':
      return '\v';

    case 'x':
      c = phase3_getc ();
      switch (c)
        {
        default:
          phase3_ungetc (c);
          phase3_ungetc ('x');
          return '\\';

        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
        case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
          break;
        }
      {
        /* For the overflow detection:
           - Valid character values in normal strings must be < 0x100.
           - In wide strings, warn and assume the programmer meant Unicode code
             points.  */
        unsigned int n_limit = (context > 0 ? 0x110000 : 0x100);
        unsigned int n;
        bool overflow;

        n = 0;
        overflow = false;

        for (;;)
          {
            switch (c)
              {
              default:
                phase3_ungetc (c);
                /* Don't warn for character literals.  */
                if (context >= 0)
                  {
                    if (context > 0 && n >= 0x80)
                      /* Hexadecimal escape sequences outside the ASCII
                         character range are platform and locale dependent.
                         Cf. <https://savannah.gnu.org/bugs/?65053>.  */
                      if_error (IF_SEVERITY_WARNING,
                                logical_file_name, line_number, (size_t)(-1), false,
                                _("hexadecimal escape sequence in wide string literal is unsupported; use \\u instead of \\x if you meant to designate a Unicode character"));
                    if (overflow)
                      if_error (IF_SEVERITY_WARNING,
                                logical_file_name, line_number, (size_t)(-1), false,
                                _("hexadecimal escape sequence out of range"));
                  }
                return (context > 0 ? UNICODE (n) : n);

              case '0': case '1': case '2': case '3': case '4':
              case '5': case '6': case '7': case '8': case '9':
                if (n < n_limit / 16)
                  n = n * 16 + c - '0';
                else
                  overflow = true;
                break;

              case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
                if (n < n_limit / 16)
                  n = n * 16 + 10 + c - 'A';
                else
                  overflow = true;
                break;

              case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
                if (n < n_limit / 16)
                  n = n * 16 + 10 + c - 'a';
                else
                  overflow = true;
                break;
              }
            c = phase3_getc ();
          }
      }

    case '0': case '1': case '2': case '3':
    case '4': case '5': case '6': case '7':
      {
        int n;

        n = 0;
        for (j = 0; j < 3; ++j)
          {
            n = n * 8 + c - '0';
            c = phase3_getc ();
            switch (c)
              {
              default:
                break;

              case '0': case '1': case '2': case '3':
              case '4': case '5': case '6': case '7':
                continue;
              }
            break;
          }
        phase3_ungetc (c);
        return n;
      }

    case 'U': case 'u':
      {
        unsigned char buf[8];
        unsigned int n;

        n = 0;
        for (j = 0; j < (c == 'u' ? 4 : 8); j++)
          {
            int c1 = phase3_getc ();

            if (c1 >= '0' && c1 <= '9')
              n = (n << 4) + (c1 - '0');
            else if (c1 >= 'A' && c1 <= 'F')
              n = (n << 4) + (c1 - 'A' + 10);
            else if (c1 >= 'a' && c1 <= 'f')
              n = (n << 4) + (c1 - 'a' + 10);
            else
              {
                phase3_ungetc (c1);
                while (--j >= 0)
                  phase3_ungetc (buf[j]);
                phase3_ungetc (c);
                return '\\';
              }

            buf[j] = c1;
          }

        if (n < 0x110000)
          return UNICODE (n);

        /* Don't warn for character literals.  */
        if (context >= 0)
          if_error (IF_SEVERITY_WARNING,
                    logical_file_name, line_number, (size_t)(-1), false,
                     _("invalid Unicode character"));

        while (--j >= 0)
          phase3_ungetc (buf[j]);
        phase3_ungetc (c);
        return '\\';
      }
    }
}


static void
unget_string_element (int c)
{
  phase3_ungetc (c);
}


/* Free the memory pointed to by a 'struct token_ty'.  */
static inline void
free_token (token_ty *tp)
{
  if (tp->type == token_type_name)
    free (tp->string);
  if (tp->type == token_type_string_literal)
    mixed_string_free (tp->mixed_string);
  if (tp->type == token_type_string_literal
      || tp->type == token_type_objc_special)
    drop_reference (tp->comment);
}


/* 5. Parse each resulting logical line as preprocessing tokens and
   white space.  Preprocessing tokens and C tokens don't always match.  */

static token_ty phase5_pushback[1];
static int phase5_pushback_length;


static void
phase5_get (token_ty *tp)
{
  int c;

  if (phase5_pushback_length)
    {
      *tp = phase5_pushback[--phase5_pushback_length];
      return;
    }
  tp->string = NULL;
  tp->number = 0;
  tp->line_number = line_number;
  c = phase4_getc ();
  switch (c)
    {
    case EOF:
      tp->type = token_type_eof;
      return;

    case '\n':
      tp->type = token_type_eoln;
      return;

    case ' ':
    case '\f':
    case '\t':
      for (;;)
        {
          c = phase4_getc ();
          switch (c)
            {
            case ' ':
            case '\f':
            case '\t':
              continue;

            default:
              phase4_ungetc (c);
              break;
            }
          break;
        }
      tp->type = token_type_white_space;
      return;

    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
    case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
    case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
    case 'V': case 'W': case 'X': case 'Y': case 'Z':
    case '_':
    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
    case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
    case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
    case 'v': case 'w': case 'x': case 'y': case 'z':
      {
        struct string_buffer buffer;
        sb_init (&buffer);
        for (;;)
          {
            sb_xappend1 (&buffer, c);
            c = phase4_getc ();
            switch (c)
              {
              case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
              case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
              case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
              case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
              case 'Y': case 'Z':
              case '_':
              case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
              case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
              case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
              case 's': case 't': case 'u': case 'v': case 'w': case 'x':
              case 'y': case 'z':
              case '0': case '1': case '2': case '3': case '4':
              case '5': case '6': case '7': case '8': case '9':
                continue;

              case '"':
                {
                  string_desc_t contents = sb_contents (&buffer);
                  const char *buf = sd_data (contents);
                  size_t buflen = sd_length (contents);

                  /* Recognize C11 / C++11 string literals.
                     See (for C) ISO 9899:2011 section 6.4.5
                     and (for C++) ISO C++ 11 section 2.14.5 [lex.string].
                     Note: The programmer who passes an UTF-8 encoded string to
                     gettext() or similar API functions will have to have called
                     bind_textdomain_codeset (DOMAIN, "UTF-8") first.  */
                  if ((buflen == 1 && (buf[0] == 'u' || buf[0] == 'U'))
                      || (buflen == 2 && buf[0] == 'u' && buf[1] == '8'))
                    {
                      sb_free (&buffer);
                      goto string_literal;
                    }
                  if (buflen == 1 && buf[0] == 'L')
                    {
                      sb_free (&buffer);
                      goto wide_string_literal;
                    }
                  /* Recognize C++11 raw string literals.
                     See ISO C++ 11 section 2.14.5 [lex.string].
                     Here it is important to properly parse all cases according to
                     the standard, otherwise our parser could get confused by
                     double-quotes inside the raw string.
                     Note: The programmer who passes an UTF-8 encoded string to
                     gettext() or similar API functions will have to have called
                     bind_textdomain_codeset (DOMAIN, "UTF-8") first.  */
                  if (cxx_extensions
                      && (buflen == 1
                          || (buflen == 2
                              && (buf[0] == 'u' || buf[0] == 'U' || buf[0] == 'L'))
                          || (buflen == 3 && buf[0] == 'u' && buf[1] == '8'))
                      && buf[buflen - 1] == 'R')
                    {
                      /* Only R and u8R raw strings can be used as gettext()
                         arguments, for type reasons.  But the programmer may have
                         defined
                           - a c16gettext function that takes a 'const char16_t *'
                             argument, or
                           - a c32gettext function that takes a 'const char32_t *'
                             argument, or
                           - a wgettext function that takes a 'const wchar_t *'
                             argument.  */
                      int starting_line_number = line_number;
                      buffer.length = 0;
                      /* Start the buffer with a closing parenthesis.  This makes the
                         parsing code below simpler.  */
                      sb_xappend1 (&buffer, ')');
                      /* Parse the initial delimiter.  */
                      for (;;)
                        {
                          bool valid_delimiter_char;

                          c = phase3_getc ();
                          switch (c)
                            {
                            case 'A': case 'B': case 'C': case 'D': case 'E':
                            case 'F': case 'G': case 'H': case 'I': case 'J':
                            case 'K': case 'L': case 'M': case 'N': case 'O':
                            case 'P': case 'Q': case 'R': case 'S': case 'T':
                            case 'U': case 'V': case 'W': case 'X': case 'Y':
                            case 'Z':
                            case 'a': case 'b': case 'c': case 'd': case 'e':
                            case 'f': case 'g': case 'h': case 'i': case 'j':
                            case 'k': case 'l': case 'm': case 'n': case 'o':
                            case 'p': case 'q': case 'r': case 's': case 't':
                            case 'u': case 'v': case 'w': case 'x': case 'y':
                            case 'z':
                            case '0': case '1': case '2': case '3': case '4':
                            case '5': case '6': case '7': case '8': case '9':
                            case '_': case '{': case '}': case '[': case ']':
                            case '#': case '<': case '>': case '%': case ':':
                            case ';': case '.': case '?': case '*': case '+':
                            case '-': case '/': case '^': case '&': case '|':
                            case '~': case '!': case '=': case ',': case '\'':
                              valid_delimiter_char = true;
                              break;
                            case '"':
                              /* A double-quote within the delimiter! This is too
                                 weird.  We don't support this.  */
                              if_error (IF_SEVERITY_WARNING,
                                        logical_file_name, starting_line_number, (size_t)(-1), false,
                                        _("a double-quote in the delimiter of a raw string literal is unsupported"));
                              FALLTHROUGH;
                            default:
                              valid_delimiter_char = false;
                              break;
                            }
                          if (!valid_delimiter_char)
                            break;

                          sb_xappend1 (&buffer, c);
                        }
                      if (c == '(')
                        {
                          struct mixed_string_buffer msb;
                          /* The state is either 0 or
                             N, after a ')' and N-1 bytes of the delimiter have been
                             encountered.  */
                          int state;

                          /* Start accumulating the string.  */
                          mixed_string_buffer_init (&msb, lc_string,
                                                    logical_file_name, line_number);
                          state = 0;

                          for (;;)
                            {
                              c = phase3_getc ();

                              /* Keep line_number in sync.  */
                              msb.line_number = line_number;

                              if (c == EOF)
                                break;

                              /* Update the state.  */
                              string_desc_t raw_contents = sb_contents (&buffer);
                              const char *raw_buf = sd_data (raw_contents);
                              size_t raw_buflen = sd_length (raw_contents);
                              if (c == (state < raw_buflen ? raw_buf[state] : '"'))
                                {
                                  if (state < raw_buflen)
                                    state++;
                                  else /* state == raw_buflen && c == '"' */
                                    {
                                      /* Finished parsing the string.  */
                                      sb_free (&buffer);
                                      tp->type = token_type_string_literal;
                                      tp->mixed_string = mixed_string_buffer_result (&msb);
                                      tp->comment = add_reference (savable_comment);
                                      return;
                                    }
                                }
                              else
                                {
                                  int i;

                                  /* None of the bytes raw_buf[0]...raw_buf[state-1]
                                     can be ')'.  */
                                  for (i = 0; i < state; i++)
                                    mixed_string_buffer_append_char (&msb, raw_buf[i]);

                                  /* But c may be ')'.  */
                                  if (c == ')')
                                    state = 1;
                                  else
                                    {
                                      mixed_string_buffer_append_char (&msb, c);
                                      state = 0;
                                    }
                                }
                            }
                        }
                      if (c == EOF)
                        {
                          if_error (IF_SEVERITY_WARNING,
                                    logical_file_name, starting_line_number, (size_t)(-1), false,
                                    _("unterminated raw string literal"));
                          sb_free (&buffer);
                          tp->type = token_type_eof;
                          return;
                        }
                      /* The warning message for c == '"' was already emitted above.  */
                      if (c != '"')
                        if_error (IF_SEVERITY_WARNING,
                                  logical_file_name, starting_line_number, (size_t)(-1), false,
                                  _("invalid raw string literal syntax"));
                      sb_free (&buffer);
                      /* To get into a sane state, read up until the next double-quote,
                         newline, or EOF.  */
                      while (!(c == EOF || c == '"' || c == '\n'))
                        c = phase3_getc ();
                      tp->type = token_type_symbol;
                      return;
                    }
                }
                FALLTHROUGH;

              default:
                phase4_ungetc (c);
                break;
              }
            break;
          }
        tp->string = sb_xdupfree_c (&buffer);
        tp->type = token_type_name;
      }
      return;

    case '.':
      c = phase4_getc ();
      phase4_ungetc (c);
      switch (c)
        {
        default:
          tp->type = token_type_symbol;
          return;

        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
          c = '.';
          break;
        }
      FALLTHROUGH;

    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
      /* The preprocessing number token is more "generous" than the C
         number tokens.  This is mostly due to token pasting (another
         thing we can ignore here).  */
      {
        struct string_buffer buffer;
        sb_init (&buffer);
        for (;;)
          {
            sb_xappend1 (&buffer, c);
            c = phase4_getc ();
            switch (c)
              {
              case 'p':
              case 'P':
                /* In C99 and C++17, 'p' and 'P' can be used as an exponent
                   marker.  */
                FALLTHROUGH;
              case 'e':
              case 'E':
                sb_xappend1 (&buffer, c);
                c = phase4_getc ();
                if (c != '+' && c != '-')
                  {
                    phase4_ungetc (c);
                    break;
                  }
                continue;

              case 'A': case 'B': case 'C': case 'D':           case 'F':
              case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
              case 'M': case 'N': case 'O':           case 'Q': case 'R':
              case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
              case 'Y': case 'Z':
              case 'a': case 'b': case 'c': case 'd':           case 'f':
              case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
              case 'm': case 'n': case 'o':           case 'q': case 'r':
              case 's': case 't': case 'u': case 'v': case 'w': case 'x':
              case 'y': case 'z':
              case '0': case '1': case '2': case '3': case '4':
              case '5': case '6': case '7': case '8': case '9':
              case '.':
                continue;

              case '_':
                if (cxx_extensions)
                  /* In C++, an underscore can be part of a preprocessing number
                     token.  */
                  continue;
                else
                  {
                    phase4_ungetc (c);
                    break;
                  }

              case '\'':
                if (cxx_extensions)
                  {
                    /* In C++14, a single-quote followed by a digit, ASCII
                       letter, or underscore can be part of a preprocessing
                       number token.  */
                    int c1 = phase4_getc ();
                    switch (c1)
                      {
                      case '0': case '1': case '2': case '3': case '4':
                      case '5': case '6': case '7': case '8': case '9':
                      case 'A': case 'B': case 'C': case 'D': case 'E':
                      case 'F': case 'G': case 'H': case 'I': case 'J':
                      case 'K': case 'L': case 'M': case 'N': case 'O':
                      case 'P': case 'Q': case 'R': case 'S': case 'T':
                      case 'U': case 'V': case 'W': case 'X': case 'Y':
                      case 'Z':
                      case 'a': case 'b': case 'c': case 'd': case 'e':
                      case 'f': case 'g': case 'h': case 'i': case 'j':
                      case 'k': case 'l': case 'm': case 'n': case 'o':
                      case 'p': case 'q': case 'r': case 's': case 't':
                      case 'u': case 'v': case 'w': case 'x': case 'y':
                      case 'z':
                      case '_':
                        sb_xappend1 (&buffer, c);
                        c = c1;
                        continue;
                      default:
                        /* The two phase4_getc() calls that returned c and c1 did
                           nothing more than to call phase3_getc(), without any
                           lookahead.  Therefore 2 pushback characters are
                           supported in this case.  */
                        phase4_ungetc (c1);
                        break;
                      }
                  }
                else
                  {
                    /* In C23, a single-quote between two hexadecimal digits
                       can be part of a number token.  It's called a "digit
                       separator".  See ISO C 23 § 6.4.4.1 and § 6.4.4.2.  */
                    string_desc_t contents = sb_contents (&buffer);
                    if (sd_length (contents) > 0)
                      {
                        char prev =
                          sd_char_at (contents, sd_length (contents) - 1);
                        if ((prev >= '0' && prev <= '9')
                            || (prev >= 'A' && prev <= 'F')
                            || (prev >= 'a' && prev <= 'f'))
                          {
                            int c1 = phase4_getc ();
                            if ((c1 >= '0' && c1 <= '9')
                                || (c1 >= 'A' && c1 <= 'F')
                                || (c1 >= 'a' && c1 <= 'f'))
                              {
                                sb_xappend1 (&buffer, c);
                                c = c1;
                                continue;
                              }
                            /* The two phase4_getc() calls that returned c and c1
                               did nothing more than to call phase3_getc(),
                               without any lookahead.  Therefore 2 pushback
                               characters are supported in this case.  */
                            phase4_ungetc (c1);
                          }
                      }
                  }
                FALLTHROUGH;
              default:
                phase4_ungetc (c);
                break;
              }
            break;
          }
        tp->type = token_type_number;
        tp->number = atol (sb_xdupfree_c (&buffer));
      }
      return;

    case '\'':
      /* We could worry about the 'L' before wide character constants,
         but ignoring it has no effect unless one of the keywords is
         "L".  Just pretend it won't happen.  Also, we don't need to
         remember the character constant.  */
      for (;;)
        {
          c = get_string_element (-1);
          if (c == SE_NEWLINE)
            {
              if_error (IF_SEVERITY_WARNING,
                        logical_file_name, line_number - 1, (size_t)(-1), false,
                        _("unterminated character constant"));
              unget_string_element ('\n');
              break;
            }
          if (c == SE_EOF || c == SE_QUOTE)
            break;
        }
      tp->type = token_type_character_constant;
      return;

    case '"':
    string_literal:
      /* We could worry about the 'L' or 'u' or 'U' before wide string
         constants, but since gettext's argument is a 'const char *', not
         a 'const wchar_t *' (for 'L') nor a 'const char16_t *' (for 'u')
         nor a 'const char32_t *' (for 'U'), the compiler would complain
         about the argument not matching the prototype.  Just pretend it
         won't happen.  */
      {
        int wide = 0;
        if (false)
          wide_string_literal: wide = 1;
        struct mixed_string_buffer msb;

        /* Start accumulating the string.  */
        mixed_string_buffer_init (&msb, lc_string,
                                  logical_file_name, line_number);

        for (;;)
          {
            c = get_string_element (wide);

            /* Keep line_number in sync.  */
            msb.line_number = line_number;

            if (c == SE_NEWLINE)
              {
                if_error (IF_SEVERITY_WARNING,
                          logical_file_name, line_number - 1, (size_t)(-1), false,
                          _("unterminated string literal"));
                unget_string_element ('\n');
                break;
              }
            if (c == SE_EOF || c == SE_QUOTES)
              break;
            if (c == SE_QUOTE)
              c = '\'';
            if (IS_UNICODE (c))
              {
                assert (UNICODE_VALUE (c) >= 0
                        && UNICODE_VALUE (c) < 0x110000);
                mixed_string_buffer_append_unicode (&msb, UNICODE_VALUE (c));
              }
            else
              mixed_string_buffer_append_char (&msb, c);
          }
        tp->type = token_type_string_literal;
        tp->mixed_string = mixed_string_buffer_result (&msb);
        tp->comment = add_reference (savable_comment);
        return;
      }

    case '(':
      tp->type = token_type_lparen;
      return;

    case ')':
      tp->type = token_type_rparen;
      return;

    case ',':
      tp->type = token_type_comma;
      return;

    case '#':
      tp->type = token_type_hash;
      return;

    case ':':
      tp->type = token_type_colon;
      return;

    case '@':
      if (objc_extensions)
        {
          tp->type = token_type_objc_special;
          tp->comment = add_reference (savable_comment);
          return;
        }
      FALLTHROUGH;

    default:
      /* We could carefully recognize each of the 2 and 3 character
         operators, but it is not necessary, as we only need to recognize
         gettext invocations.  Don't bother.  */
      tp->type = token_type_symbol;
      return;
    }
}


/* Supports only one pushback token.  */
static void
phase5_unget (token_ty *tp)
{
  if (tp->type != token_type_eof)
    {
      if (phase5_pushback_length == SIZEOF (phase5_pushback))
        abort ();
      phase5_pushback[phase5_pushback_length++] = *tp;
    }
}


/* X. Recognize a leading # symbol.  Leave leading hash as a hash, but
   turn hash in the middle of a line into a plain symbol token.  This
   makes the phase 6 easier.  */

static void
phaseX_get (token_ty *tp)
{
  static bool middle;   /* false at the beginning of a line, true otherwise.  */

  phase5_get (tp);

  if (tp->type == token_type_eoln || tp->type == token_type_eof)
    middle = false;
  else
    {
      if (middle)
        {
          /* Turn hash in the middle of a line into a plain symbol token.  */
          if (tp->type == token_type_hash)
            tp->type = token_type_symbol;
        }
      else
        {
          /* When we see leading whitespace followed by a hash sign,
             discard the leading white space token.  The hash is all
             phase 6 is interested in.  */
          if (tp->type == token_type_white_space)
            {
              token_ty next;

              phase5_get (&next);
              if (next.type == token_type_hash)
                *tp = next;
              else
                phase5_unget (&next);
            }
          middle = true;
        }
    }
}


/* 6. Recognize and carry out directives (it also expands macros on
   non-directive lines, which we do not do here).  The only directive
   we care about are the #line and #define directive.  We throw all the
   others away.  */

static token_ty phase6_pushback[2];
static int phase6_pushback_length;


static void
phase6_get (token_ty *tp)
{
  static token_ty *buf;
  static int bufmax;
  int bufpos;
  int j;

  if (phase6_pushback_length)
    {
      *tp = phase6_pushback[--phase6_pushback_length];
      return;
    }
  for (;;)
    {
      /* Get the next token.  If it is not a '#' at the beginning of a
         line (ignoring whitespace), return immediately.  */
      phaseX_get (tp);
      if (tp->type != token_type_hash)
        return;

      /* Accumulate the rest of the directive in a buffer, until the
         "define" keyword is seen or until end of line.  */
      bufpos = 0;
      for (;;)
        {
          phaseX_get (tp);
          if (tp->type == token_type_eoln || tp->type == token_type_eof)
            break;

          /* Before the "define" keyword and inside other directives
             white space is irrelevant.  So just throw it away.  */
          if (tp->type != token_type_white_space)
            {
              /* If it is a #define directive, return immediately,
                 thus treating the body of the #define directive like
                 normal input.  */
              if (bufpos == 0
                  && tp->type == token_type_name
                  && strcmp (tp->string, "define") == 0)
                return;

              /* Accumulate.  */
              if (bufpos >= bufmax)
                {
                  bufmax = 2 * bufmax + 10;
                  buf = xrealloc (buf, bufmax * sizeof (buf[0]));
                }
              buf[bufpos++] = *tp;
            }
        }

      /* If it is a #line directive, with no macros to expand, act on
         it.  Ignore all other directives.  */
      if (bufpos >= 3 && buf[0].type == token_type_name
          && strcmp (buf[0].string, "line") == 0
          && buf[1].type == token_type_number
          && buf[2].type == token_type_string_literal)
        {
          logical_file_name = mixed_string_contents (buf[2].mixed_string);
          line_number = buf[1].number;
        }
      if (bufpos >= 2 && buf[0].type == token_type_number
          && buf[1].type == token_type_string_literal)
        {
          logical_file_name = mixed_string_contents (buf[1].mixed_string);
          line_number = buf[0].number;
        }

      /* Release the storage held by the directive.  */
      for (j = 0; j < bufpos; ++j)
        free_token (&buf[j]);

      /* We must reset the selected comments.  */
      savable_comment_reset ();
    }
}


/* Supports 2 tokens of pushback.  */
static void
phase6_unget (token_ty *tp)
{
  if (tp->type != token_type_eof)
    {
      if (phase6_pushback_length == SIZEOF (phase6_pushback))
        abort ();
      phase6_pushback[phase6_pushback_length++] = *tp;
    }
}


/* 8a. Convert ISO C 99 section 7.8.1 format string directives to string
   literal placeholders.  */

/* Test for an ISO C 99 section 7.8.1 format string directive.  */
static bool
is_inttypes_macro (const char *name)
{
  /* Syntax:
     P R I { d | i | o | u | x | X }
     { { | LEAST | FAST } { 8 | 16 | 32 | 64 } | MAX | PTR }  */
  if (name[0] == 'P' && name[1] == 'R' && name[2] == 'I')
    {
      name += 3;
      if (name[0] == 'd' || name[0] == 'i' || name[0] == 'o' || name[0] == 'u'
          || name[0] == 'x' || name[0] == 'X')
        {
          name += 1;
          if (name[0] == 'M' && name[1] == 'A' && name[2] == 'X'
              && name[3] == '\0')
            return true;
          if (name[0] == 'P' && name[1] == 'T' && name[2] == 'R'
              && name[3] == '\0')
            return true;
          if (name[0] == 'L' && name[1] == 'E' && name[2] == 'A'
              && name[3] == 'S' && name[4] == 'T')
            name += 5;
          else if (name[0] == 'F' && name[1] == 'A' && name[2] == 'S'
                   && name[3] == 'T')
            name += 4;
          if (name[0] == '8' && name[1] == '\0')
            return true;
          if (name[0] == '1' && name[1] == '6' && name[2] == '\0')
            return true;
          if (name[0] == '3' && name[1] == '2' && name[2] == '\0')
            return true;
          if (name[0] == '6' && name[1] == '4' && name[2] == '\0')
            return true;
        }
    }
  return false;
}

static void
phase8a_get (token_ty *tp)
{
  phase6_get (tp);
  if (tp->type == token_type_name && is_inttypes_macro (tp->string))
    {
      /* Turn PRIdXXX into "<PRIdXXX>".  */
      char *new_string = xasprintf ("<%s>", tp->string);
      free (tp->string);
      tp->mixed_string =
        mixed_string_alloc_utf8 (new_string, lc_string,
                                 logical_file_name, line_number);
      tp->comment = add_reference (savable_comment);
      tp->type = token_type_string_literal;
    }
}

/* Supports 2 tokens of pushback.  */
static inline void
phase8a_unget (token_ty *tp)
{
  phase6_unget (tp);
}


/* 8b. Drop whitespace.  */
static void
phase8b_get (token_ty *tp)
{
  for (;;)
    {
      phase8a_get (tp);

      if (tp->type == token_type_white_space)
        continue;
      if (tp->type == token_type_eoln)
        {
          /* We have to track the last occurrence of a string.  One
             mode of xgettext allows to group an extracted message
             with a comment for documentation.  The rule which states
             which comment is assumed to be grouped with the message
             says it should immediately precede it.  Our
             interpretation: between the last line of the comment and
             the line in which the keyword is found must be no line
             with non-white space tokens.  */
          ++newline_count;
          if (last_non_comment_line > last_comment_line)
            savable_comment_reset ();
          continue;
        }
      break;
    }
}

/* Supports 2 tokens of pushback.  */
static inline void
phase8b_unget (token_ty *tp)
{
  phase8a_unget (tp);
}


/* 8c. In ObjectiveC mode, drop '@' before a literal string.  We need to
   do this before performing concatenation of adjacent string literals.  */
static void
phase8c_get (token_ty *tp)
{
  token_ty tmp;

  phase8b_get (tp);
  if (tp->type != token_type_objc_special)
    return;
  phase8b_get (&tmp);
  if (tmp.type != token_type_string_literal)
    {
      phase8b_unget (&tmp);
      return;
    }
  /* Drop the '@' token and return immediately the following string.  */
  drop_reference (tmp.comment);
  tmp.comment = tp->comment;
  *tp = tmp;
}

/* Supports only one pushback token.  */
static inline void
phase8c_unget (token_ty *tp)
{
  phase8b_unget (tp);
}


/* 8. Concatenate adjacent string literals to form single string
   literals (because we don't expand macros, there are a few things we
   will miss).  */

static void
phase8_get (token_ty *tp)
{
  phase8c_get (tp);
  if (tp->type != token_type_string_literal)
    return;
  for (;;)
    {
      token_ty tmp;

      phase8c_get (&tmp);
      if (tmp.type != token_type_string_literal)
        {
          phase8c_unget (&tmp);
          return;
        }
      tp->mixed_string =
        mixed_string_concat_free1 (tp->mixed_string, tmp.mixed_string);
      free_token (&tmp);
    }
}


/* ===================== Reading of high-level tokens.  ==================== */


enum xgettext_token_type_ty
{
  xgettext_token_type_eof,
  xgettext_token_type_keyword,
  xgettext_token_type_symbol,
  xgettext_token_type_lparen,
  xgettext_token_type_rparen,
  xgettext_token_type_comma,
  xgettext_token_type_colon,
  xgettext_token_type_string_literal,
  xgettext_token_type_other
};
typedef enum xgettext_token_type_ty xgettext_token_type_ty;

typedef struct xgettext_token_ty xgettext_token_ty;
struct xgettext_token_ty
{
  xgettext_token_type_ty type;

  /* This field is used only for xgettext_token_type_keyword.  */
  const struct callshapes *shapes;

  /* This field is used only for xgettext_token_type_keyword,
     xgettext_token_type_symbol.  */
  char *string;

  /* This field is used only for xgettext_token_type_string_literal.  */
  mixed_string_ty *mixed_string;

  /* This field is used only for xgettext_token_type_string_literal.  */
  refcounted_string_list_ty *comment;

  /* This field is used only for xgettext_token_type_keyword,
     xgettext_token_type_string_literal.  */
  lex_pos_ty pos;
};


/* 9. Convert the remaining preprocessing tokens to C tokens and
   discards any white space from the translation unit.  */

static void
x_c_lex (xgettext_token_ty *tp)
{
  for (;;)
    {
      token_ty token;
      void *keyword_value;

      phase8_get (&token);
      switch (token.type)
        {
        case token_type_eof:
          tp->type = xgettext_token_type_eof;
          return;

        case token_type_name:
          last_non_comment_line = newline_count;

          if (hash_find_entry (objc_extensions ? &objc_keywords : &c_keywords,
                               token.string, strlen (token.string),
                               &keyword_value)
              == 0)
            {
              tp->type = xgettext_token_type_keyword;
              tp->shapes = (const struct callshapes *) keyword_value;
              tp->pos.file_name = logical_file_name;
              tp->pos.line_number = token.line_number;
            }
          else
            tp->type = xgettext_token_type_symbol;
          tp->string = token.string;
          return;

        case token_type_lparen:
          last_non_comment_line = newline_count;

          tp->type = xgettext_token_type_lparen;
          return;

        case token_type_rparen:
          last_non_comment_line = newline_count;

          tp->type = xgettext_token_type_rparen;
          return;

        case token_type_comma:
          last_non_comment_line = newline_count;

          tp->type = xgettext_token_type_comma;
          return;

        case token_type_colon:
          last_non_comment_line = newline_count;

          tp->type = xgettext_token_type_colon;
          return;

        case token_type_string_literal:
          last_non_comment_line = newline_count;

          tp->type = xgettext_token_type_string_literal;
          tp->mixed_string = token.mixed_string;
          tp->comment = token.comment;
          tp->pos.file_name = logical_file_name;
          tp->pos.line_number = token.line_number;
          return;

        case token_type_objc_special:
          drop_reference (token.comment);
          FALLTHROUGH;

        default:
          last_non_comment_line = newline_count;

          tp->type = xgettext_token_type_other;
          return;
        }
    }
}


/* ========================= Extracting strings.  ========================== */


/* Context lookup table.  */
static flag_context_list_table_ty *flag_context_list_table;


/* Maximum supported nesting depth.
   ISO C 23 § 5.2.4.1.(1) requires 63 "nesting levels of parenthesized
   expressions within a full expression"; then 1000 is more than enough.  */
#define MAX_NESTING_DEPTH 1000

/* Current nesting depth.  */
static int nesting_depth;


/* The file is broken into tokens.  Scan the token stream, looking for
   a keyword, followed by a left paren, followed by a string.  When we
   see this sequence, we have something to remember.  We assume we are
   looking at a valid C or C++ program, and leave the complaints about
   the grammar to the compiler.

     Normal handling: Look for
       keyword ( ... msgid ... )
     Plural handling: Look for
       keyword ( ... msgid ... msgid_plural ... )

   We use recursion because the arguments before msgid or between msgid
   and msgid_plural can contain subexpressions of the same form.  */


/* Extract messages until the next balanced closing parenthesis.
   Extracted messages are added to MLP.
   Return true upon eof, false upon closing parenthesis.  */
static bool
extract_parenthesized (message_list_ty *mlp,
                       flag_region_ty *outer_region,
                       flag_context_list_iterator_ty context_iter,
                       struct arglist_parser *argparser)
{
  /* Current argument number.  */
  int arg = 1;
  /* 0 when no keyword has been seen.  1 right after a keyword is seen.  */
  int state;
  /* Parameters of the keyword just seen.  Defined only in state 1.  */
  const struct callshapes *next_shapes = NULL;
  /* Context iterator that will be used if the next token is a '('.  */
  flag_context_list_iterator_ty next_context_iter =
    passthrough_context_list_iterator;
  /* Context iterator that will be used if the next token is a ':'.
     (Objective C selector syntax.)  */
  flag_context_list_iterator_ty selectorcall_context_iter =
    passthrough_context_list_iterator;
  /* Current region.  */
  flag_region_ty *inner_region =
    inheriting_region (outer_region,
                       flag_context_list_iterator_advance (&context_iter));

  /* Start state is 0.  */
  state = 0;

  for (;;)
    {
      xgettext_token_ty token;

      x_c_lex (&token);
      switch (token.type)
        {
        case xgettext_token_type_keyword:
          next_shapes = token.shapes;
          state = 1;
          goto keyword_or_symbol;

        case xgettext_token_type_symbol:
          state = 0;
        keyword_or_symbol:
          next_context_iter =
            flag_context_list_iterator (
              flag_context_list_table_lookup (
                flag_context_list_table,
                token.string, strlen (token.string)));
          if (objc_extensions)
            {
              size_t token_string_len = strlen (token.string);
              token.string = xrealloc (token.string, token_string_len + 2);
              token.string[token_string_len] = ':';
              token.string[token_string_len + 1] = '\0';
              selectorcall_context_iter =
                flag_context_list_iterator (
                  flag_context_list_table_lookup (
                    flag_context_list_table,
                    token.string, token_string_len + 1));
            }
          free (token.string);
          continue;

        case xgettext_token_type_lparen:
          if (++nesting_depth > MAX_NESTING_DEPTH)
            if_error (IF_SEVERITY_FATAL_ERROR,
                      logical_file_name, line_number, (size_t)(-1), false,
                      _("too many open parentheses"));
          if (extract_parenthesized (mlp, inner_region, next_context_iter,
                                     arglist_parser_alloc (mlp,
                                                           state ? next_shapes : NULL)))
            {
              arglist_parser_done (argparser, arg);
              unref_region (inner_region);
              return true;
            }
          nesting_depth--;
          next_context_iter = null_context_list_iterator;
          selectorcall_context_iter = null_context_list_iterator;
          state = 0;
          continue;

        case xgettext_token_type_rparen:
          arglist_parser_done (argparser, arg);
          unref_region (inner_region);
          return false;

        case xgettext_token_type_comma:
          arg++;
          unref_region (inner_region);
          inner_region =
            inheriting_region (outer_region,
                               flag_context_list_iterator_advance (
                                 &context_iter));
          next_context_iter = passthrough_context_list_iterator;
          selectorcall_context_iter = passthrough_context_list_iterator;
          state = 0;
          continue;

        case xgettext_token_type_colon:
          if (objc_extensions)
            {
              context_iter = selectorcall_context_iter;
              assign_new_region (inner_region,
                inheriting_region (inner_region,
                                   flag_context_list_iterator_advance (
                                     &context_iter)));
              next_context_iter = passthrough_context_list_iterator;
              selectorcall_context_iter = passthrough_context_list_iterator;
            }
          else
            {
              next_context_iter = null_context_list_iterator;
              selectorcall_context_iter = null_context_list_iterator;
            }
          state = 0;
          continue;

        case xgettext_token_type_string_literal:
          {
            if (extract_all)
              {
                char *string = mixed_string_contents (token.mixed_string);
                mixed_string_free (token.mixed_string);
                remember_a_message (mlp, NULL, string, true, false,
                                    inner_region, &token.pos,
                                    NULL, token.comment, false);
              }
            else
              arglist_parser_remember (argparser, arg, token.mixed_string,
                                       inner_region,
                                       token.pos.file_name,
                                       token.pos.line_number,
                                       token.comment, false);
            drop_reference (token.comment);
          }
          next_context_iter = null_context_list_iterator;
          selectorcall_context_iter = null_context_list_iterator;
          state = 0;
          continue;

        case xgettext_token_type_other:
          next_context_iter = null_context_list_iterator;
          selectorcall_context_iter = null_context_list_iterator;
          state = 0;
          continue;

        case xgettext_token_type_eof:
          arglist_parser_done (argparser, arg);
          unref_region (inner_region);
          return true;

        default:
          abort ();
        }
    }
}


static void
extract_whole_file (FILE *f,
                    const char *real_filename, const char *logical_filename,
                    flag_context_list_table_ty *flag_table,
                    msgdomain_list_ty *mdlp)
{
  message_list_ty *mlp = mdlp->item[0]->messages;

  fp = f;
  real_file_name = real_filename;
  logical_file_name = xstrdup (logical_filename);
  line_number = 1;

  phase1_pushback_length = 0;
  phase2_pushback_length = 0;
  phase3_pushback_length = 0;

  last_comment_line = -1;
  last_non_comment_line = -1;
  newline_count = 0;

  phase5_pushback_length = 0;
  phase6_pushback_length = 0;

  flag_context_list_table = flag_table;
  nesting_depth = 0;

  init_keywords ();

  /* Eat tokens until eof is seen.  When extract_parenthesized returns
     due to an unbalanced closing parenthesis, just restart it.  */
  while (!extract_parenthesized (mlp, null_context_region (), null_context_list_iterator,
                                 arglist_parser_alloc (mlp, NULL)))
    ;

  /* Close scanner.  */
  fp = NULL;
  real_file_name = NULL;
  logical_file_name = NULL;
  line_number = 0;
}


void
extract_c (FILE *f,
           const char *real_filename, const char *logical_filename,
           flag_context_list_table_ty *flag_table,
           msgdomain_list_ty *mdlp)
{
  objc_extensions = false;
  cxx_extensions = false;
  extract_whole_file (f, real_filename, logical_filename, flag_table, mdlp);
}

void
extract_cxx (FILE *f,
             const char *real_filename, const char *logical_filename,
             flag_context_list_table_ty *flag_table,
             msgdomain_list_ty *mdlp)
{
  objc_extensions = false;
  cxx_extensions = true;
  extract_whole_file (f, real_filename, logical_filename, flag_table, mdlp);
}

void
extract_objc (FILE *f,
              const char *real_filename, const char *logical_filename,
              flag_context_list_table_ty *flag_table,
              msgdomain_list_ty *mdlp)
{
  objc_extensions = true;
  cxx_extensions = false;
  extract_whole_file (f, real_filename, logical_filename, flag_table, mdlp);
}
